﻿#include "FITKAcousticsVTKGraphObjectNodes.h"
#include "FITKAcousticsVTKGraphActor.h"

#include <math.h>

// VTK
#include <vtkProperty.h>
#include <vtkUnstructuredGrid.h>
#include <vtkPoints.h>
#include <vtkPointData.h>
#include <vtkIntArray.h>
#include <vtkIdList.h>
#include <vtkGlyph3D.h>
#include <vtkCubeSource.h>
#include <vtkCellType.h>
#include <vtkSelectionNode.h>
#include <vtkSelection.h>
#include <vtkExtractSelection.h>
#include <vtkIdTypeArray.h>

// Graph
#include "FITKAcousticsVTKCommons.h"
#include "FITKAcousticsVTKSharedDataManager.h"
#include "FITKAcousticsVTKGraphObjectSelectSEANodes.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"

// Data
#include "FITK_Component/FITKAcousticsModel/FITKAcousticsSEAModel.h"
#include "FITK_Component/FITKAcousticsModel/FITKAcousticsDiscNodeList.h"
#include "FITK_Component/FITKAcousticsModel/FITKAcousticsDiscModel.h"
#include "FITK_Kernel/FITKAppFramework/FITKGlobalData.h"
#include "FITK_Kernel/FITKAppFramework/FITKAppFramework.h"

namespace Exchange
{
    FITKAcousticsVTKGraphObjectNodes::FITKAcousticsVTKGraphObjectNodes(Acoustics::FITKAcousticsDiscNodeList* nodes)
        : FITKAcousticsVTKGraphObject3D(nodes)
    {
        // Return if the input is nullptr.
        if (!nodes)
        {
            return;
        }

        // Intiailize the polygon offset.
        m_polygonOffset = FITKAcousticsVTKCommons::s_polygonOffsetTopMarks;

        // Initialize the vtk object.
        init();
        
        // Generate the data set.
        if (!generateDataSet(nodes))
        {
            return;
        }

        // Generate the nodes actor.
        generateGraph();

        // Save the data type.
        m_graphInfo.Type = FITKAcousticsVTKCommons::DiscNodesGraph;

        // Set the layer need to be rendered.
        setRenderLayer(0, 2);
    }

    FITKAcousticsVTKGraphObjectNodes::~FITKAcousticsVTKGraphObjectNodes()
    {  
        // Delete vtk objects.
        deleteVtkObj(m_nodeIdArray);
        deleteVtkObj(m_ugrid);      
        deleteVtkObj(m_cubeSource);
        deleteVtkObj(m_glyph);
        deleteVtkObj(m_selectNode);
        deleteVtkObj(m_section);
        deleteVtkObj(m_extractSelection);
        deleteVtkObj(m_idArray); 
    }

    void FITKAcousticsVTKGraphObjectNodes::init()
    {
        // Create the highlight selector.
        m_highlightSelector = new FITKAcousticsVTKGraphObjectSelectSEANodes;
        m_addinGraphObjList.push_back(m_highlightSelector);

        // Create the grid.
        m_ugrid = vtkUnstructuredGrid::New();
        vtkPoints* points = FITKAcousticsVTKSharedDataManager::GetInstance()->getSEANodesPoints(true);
        m_ugrid->SetPoints(points);
        m_nodeIdArray = vtkIntArray::New();
        m_nodeIdArray->SetNumberOfComponents(1);
        m_nodeIdArray->SetName(QStringToCharA(m_nodeIdArrayName));
        m_ugrid->GetPointData()->AddArray(m_nodeIdArray);

        // Initialize the extractor.
        //@{
        m_selectNode = vtkSelectionNode::New();
        m_section = vtkSelection::New();
        m_extractSelection = vtkExtractSelection::New();
        m_selectNode->SetContentType(vtkSelectionNode::INDICES);
        m_selectNode->SetFieldType(vtkSelectionNode::SelectionField::POINT);
        m_section->AddNode(m_selectNode);
        m_extractSelection->SetInputData(1, m_section);

        m_idArray = vtkIdTypeArray::New();
        m_selectNode->SetSelectionList(m_idArray);
        m_extractSelection->SetInputData(m_ugrid);
        //@}

        // Initialize the glyph size.
        m_glyphSize = 20.;

        // Create the source and glyph.
        //@{
        m_cubeSource = vtkCubeSource::New();
        m_cubeSource->SetCenter(0., 0., 0.);
        m_cubeSource->SetXLength(m_glyphSize);
        m_cubeSource->SetYLength(m_glyphSize);
        m_cubeSource->SetZLength(m_glyphSize);

        m_glyph = vtkGlyph3D::New();
        m_glyph->SetSourceConnection(m_cubeSource->GetOutputPort());
        m_glyph->SetInputConnection(m_extractSelection->GetOutputPort());
        //@}
    }

    Acoustics::FITKAcousticsDiscNodeList* FITKAcousticsVTKGraphObjectNodes::getSEANodesData()
    {
        // Get the SEA nodes data list.
        Acoustics::FITKAcousticsDiscModel* discModel = FITKGLODATA->getMeshData<Acoustics::FITKAcousticsDiscModel>();
        if (!discModel)return nullptr;

        // Get the SEA nodes data list.
        Acoustics::FITKAcousticsDiscNodeList* nodes = discModel->getNodes();
        if (!nodes)
        {
            return nullptr;
        }
        return nodes;
    }

    void FITKAcousticsVTKGraphObjectNodes::updateGlyphSizeByBounds()
    {
        if (!m_cubeSource || !m_glyph)
        {
            return;
        }

        // Calculate the glyph size.
        m_glyphSize = FITKAcousticsVTKSharedDataManager::GetInstance()->getNodeWorldSizeByRatioWithDensity();

        m_cubeSource->SetXLength(m_glyphSize);
        m_cubeSource->SetYLength(m_glyphSize);
        m_cubeSource->SetZLength(m_glyphSize);

        // Update the pipeline.
        m_glyph->Update();
    }

    void FITKAcousticsVTKGraphObjectNodes::update(bool forceUpdate)
    {
        if (!m_ugrid)
        {
            return;
        }

        // Get the SEA nodes data.
        Acoustics::FITKAcousticsDiscNodeList* nodes = getSEANodesData();
        if (!nodes)
        {
            return;
        }

        // Check the node count only.
        int nNodes = nodes->getNodeCount();
        if (nNodes == m_lastNodeCount && !forceUpdate)
        {
            // Update visibility only.
            updateVisibility();
            return;
        }

        // Reset the data.
        FITKAcousticsVTKSharedDataManager::GetInstance()->getSEANodesPoints(true);
        resetVtkObj(m_ugrid);
        resetVtkObj(m_idArray);
        resetVtkObj(m_nodeIdArray);
        m_ugrid->GetPointData()->AddArray(m_nodeIdArray);

        if (m_selectNode)
        {
            m_selectNode->Modified();
        }

        if (m_highlightSelector)
        {
            m_highlightSelector->clearData();
        }

        // Re-generate the grid data.
        generateDataSet(nodes);

        // Update visibility.
        updateVisibility();

        // Update the actors.
        m_ugrid->Modified();
    }

    void FITKAcousticsVTKGraphObjectNodes::updateVisibility()
    {
        // Get the runtime settings for visibility.
        bool showSEAUsedNodes = getRuntimeSettingAs<bool>(RTS_KEY_VISIBLE_GRAPH_NODES, false);
        bool showSEAUnusedNodes = getRuntimeSettingAs<bool>(RTS_KEY_VISIBLE_UNUSED_NODES, false);

        // Update the visibility with the flags.
        //@{
        bool hasUpdated = false;

        // Show all.
        if (showSEAUsedNodes && showSEAUnusedNodes && !hasUpdated)
        {
            updateByAll();
            hasUpdated = true;
        }

        // Hide all.
        if (!showSEAUsedNodes && !showSEAUnusedNodes && !hasUpdated)
        {
            updateByNone();
            hasUpdated = true;
        }

        // Show used nodes only.
        if (showSEAUsedNodes && !showSEAUnusedNodes && !hasUpdated)
        {
            updateByVisibleObjs();
            hasUpdated = true;
        }

        // Show unused nodes only.
        if (!showSEAUsedNodes && showSEAUnusedNodes && !hasUpdated)
        {
            updateByUnused();
            hasUpdated = true;
        }
        //@}

        // Get the SEA nodes data.
        Acoustics::FITKAcousticsDiscNodeList* nodes = getSEANodesData();
        if (!nodes)
        {
            return;
        }

        // Deal the problem: 
        // The cube glyph cannot be hidden while the nodes count is zero.
        bool hasValidNode = nodes->getNodeCount() != 0;
        m_fActorGlyph->SetVisibility(hasValidNode);

        if (m_highlightSelector)
        {
            m_highlightSelector->clearData();
            m_highlightSelector->setVisible(false);
        }
    }

    void FITKAcousticsVTKGraphObjectNodes::updateByVisibleObjs()
    {
        // Get the unused node ids.
        Acoustics::FITKAcousticsDiscModel* discModel = FITKGLODATA->getMeshData<Acoustics::FITKAcousticsDiscModel>();
        if (!discModel)return;
        Acoustics::FITKAcousticsDiscNodeList* nodes = dynamic_cast<Acoustics::FITKAcousticsDiscNodeList*>(_dataObj);
        if (!nodes || !m_fActorGlyph || !m_idArray || !m_ugrid)
        {
            return;
        }

        // Reset the ids.
        resetVtkObj(m_idArray);

        // Get the SEA nodes data list.
        QList<int> unusedIds = discModel->getUnUsedNodeIDs();

        int nNodes = nodes->getNodeCount();
        QVector<bool> unusedFlags(nNodes);
        unusedFlags.fill(0);

        // Fill the ids with the used nodes indice.
        for (const int & id : unusedIds)
        {
            int vtkIndex = nodes->getNodeIndexByID(id);
            if (vtkIndex < 0)
            {
                continue;
            }

            unusedFlags[vtkIndex] = 1;
        }

        for (int i = 0; i < nNodes; i++)
        {
            if (!unusedFlags[i])
            {
                m_idArray->InsertNextValue(i);
            }
        }

        // Update the pipeline.
        m_ugrid->Modified();

        // Show the grid manully.
        m_fActorGlyph->SetVisibility(true);
    }

    void FITKAcousticsVTKGraphObjectNodes::updateByUnused()
    {
        Acoustics::FITKAcousticsDiscModel* discModel = FITKGLODATA->getMeshData<Acoustics::FITKAcousticsDiscModel>();
        if (!discModel)return;
        Acoustics::FITKAcousticsDiscNodeList* nodes = dynamic_cast<Acoustics::FITKAcousticsDiscNodeList*>(_dataObj);
        if (!nodes || !m_fActorGlyph || !m_idArray || !m_ugrid)
        {
            return;
        }

        // Reset the ids.
        resetVtkObj(m_idArray);

        // Get the unused node ids.
        QList<int> unusedIds = discModel->getUnUsedNodeIDs();

        // Fill the ids with the unused nodes indice.
        for (const int & id : unusedIds)
        {
            int vtkIndex = nodes->getNodeIndexByID(id);
            if (vtkIndex < 0)
            {
                continue;
            }

            m_idArray->InsertNextValue(vtkIndex);
        }

        // Update the pipeline.
        m_ugrid->Modified();

        // Show the grid manully.
        m_fActorGlyph->SetVisibility(true);
    }

    void FITKAcousticsVTKGraphObjectNodes::updateByAll()
    {
        if (!m_fActorGlyph || !m_idArray || !m_ugrid)
        {
            return;
        }

        // Reset the ids.
        resetVtkObj(m_idArray);

        // Fill the ids with the nodes count.
        int nNodes = m_ugrid->GetNumberOfPoints();
        for (int i = 0; i < nNodes; i++)
        {
            m_idArray->InsertNextValue(i);
        }

        // Update the pipeline.
        m_ugrid->Modified();

        // Show the grid manully.
        m_fActorGlyph->SetVisibility(true);
    }

    void FITKAcousticsVTKGraphObjectNodes::updateByNone()
    {
        if (!m_ugrid || !m_fActorGlyph)
        {
            return;
        }

        // Clear the extract ids.
        resetVtkObj(m_idArray);

        // Update the pipeline.
        m_ugrid->Modified();

        // Hide the grid manully.
        m_fActorGlyph->SetVisibility(false);
    }

    void FITKAcousticsVTKGraphObjectNodes::highlight(FITKAcousticsVTKCommons::ShapeType type, QColor color)
    {
        // Show the actor and initialize the data.
        if (!m_highlightSelector)
        {
            return;
        }

        switch (type)
        {
        case Exchange::FITKAcousticsVTKCommons::ShapeTypeNone:
            break;
        case Exchange::FITKAcousticsVTKCommons::ModelVertex:
            break;
        case Exchange::FITKAcousticsVTKCommons::MeshNode:
            break;
        default:
            return;
        }

        vtkDataSet* meshDataSet = getMesh(type);
        if (!meshDataSet)
        {
            return;
        }

        QVector<int> indice;
        for (int i = 0; i < meshDataSet->GetNumberOfCells(); i++)
        {
            indice.push_back(i);
        }

        // Check the color.
        if (!color.isValid())
        {
            color = Qt::red;
        }

        m_highlightSelector->setGlyphSize(getGlyphSize());
        m_highlightSelector->setColor(color);
        m_highlightSelector->setSelectData(indice);
        m_highlightSelector->setVisible(getDataVisibility());

        // Save the highlight flag.
        this->Superclass::highlight(type, color);
    }

    void FITKAcousticsVTKGraphObjectNodes::disHighlight()
    {
        // Hide the actor only.
        if (m_highlightSelector)
        {
            m_highlightSelector->clearData();
            m_highlightSelector->setVisible(false);
        }

        // Save the highlight flag.
        this->Superclass::disHighlight();
    }

    void FITKAcousticsVTKGraphObjectNodes::advanceHighlight(FITKAcousticsVTKCommons::ShapeType type, QVector<int> indice, QColor color)
    {
        // Fill the ids with the unused nodes indice.
        Acoustics::FITKAcousticsDiscNodeList* nodes = getSEANodesData();
        if (!nodes)
        {
            return;
        }

        // Show the actor and initialize the data.
        if (!m_highlightSelector)
        {
            return;
        }

        if (indice.isEmpty())
        {
            return;
        }

        switch (type)
        {
        case Exchange::FITKAcousticsVTKCommons::ShapeTypeNone:
        case Exchange::FITKAcousticsVTKCommons::ModelVertex:
        case Exchange::FITKAcousticsVTKCommons::MeshNode:
            break;
        default:
            return;
        }

        vtkDataSet* meshDataSet = getMesh(type);
        if (!meshDataSet)
        {
            return;
        }

        // Ids to indice.
        QVector<int> indiceVtk;
        for (const int & id : indice)
        {
            int index = nodes->getNodeIndexByID(id);
            if (index >= 0)
            {
                indiceVtk.push_back(index);
            }
        }

        // Check the color.
        if (!color.isValid())
        {
            color = Qt::red;
        }

        m_highlightSelector->setGlyphSize(getGlyphSize());
        m_highlightSelector->setColor(color);
        m_highlightSelector->setSelectData(indiceVtk);
        m_highlightSelector->setVisible(getDataVisibility());

        // Save the highlight flag.
        this->Superclass::advanceHighlight(type, indiceVtk, color);
    }

    void FITKAcousticsVTKGraphObjectNodes::disAdvanceHighlight()
    {
        // Clear data and hide the actor.
        if (m_highlightSelector)
        {
            m_highlightSelector->clearData();
            m_highlightSelector->setVisible(false);
        }

        // Save the highlight flag.
        this->Superclass::disAdvanceHighlight();
    }

    void FITKAcousticsVTKGraphObjectNodes::setPickMode(FITKAcousticsVTKCommons::ShapePickMode mode)
    {
        if (!m_fActorGlyph)
        {
            return;
        }

        // Enable or disable pickablility.
        switch (mode)
        {
        case Exchange::FITKAcousticsVTKCommons::PickVertex:
        case Exchange::FITKAcousticsVTKCommons::PickNode:
        case Exchange::FITKAcousticsVTKCommons::PickAll:
            m_fActorGlyph->SetPickable(true);
            break;
        default:
            m_fActorGlyph->SetPickable(false);
            break;
        }
    }

    int FITKAcousticsVTKGraphObjectNodes::getShapeIdByVTKCellId(int vtkId, FITKAcousticsVTKCommons::ShapeMeshAbsEnum topAbsShapeType)
    {
        Q_UNUSED(topAbsShapeType);

        if (!m_glyph)
        {
            return -1;
        }

        m_glyph->Update();

        // Get the current nodes data.
        vtkDataSet* glyphGrid = vtkDataSet::SafeDownCast(m_glyph->GetOutput());
        if (!glyphGrid)
        {
            return -1;
        }

        // Get the cell count and check the index.
        int nCells = glyphGrid->GetNumberOfCells();
        if (vtkId < 0 || vtkId >= nCells)
        {
            return -1;
        }

        // Get the data array from the output point data.
        vtkPointData* pointData = glyphGrid->GetPointData();
        if (!pointData)
        {
            return -1;
        }

        vtkIntArray* outArr = vtkIntArray::SafeDownCast(pointData->GetArray(QStringToCharA(m_nodeIdArrayName)));
        if (!outArr)
        {
            return -1;
        }

        // Get the first point of the given cell( by index ).
        vtkCell* cell = glyphGrid->GetCell(vtkId);
        if (!cell)
        {
            return -1;
        }

        int iPt = cell->GetPointId(0);

        // Get the node id from the array.
        return outArr->GetValue(iPt);
    }

    const QVector<int> FITKAcousticsVTKGraphObjectNodes::getVTKCellIdsByShapeId(int shapeId, FITKAcousticsVTKCommons::ShapeMeshAbsEnum topAbsShapeType)
    {
        Q_UNUSED(topAbsShapeType);

        QVector<int> indice;

        // Fill the ids with the unused nodes indice.
        Acoustics::FITKAcousticsDiscNodeList* nodes = getSEANodesData();
        if (!nodes)
        {
            return indice;
        }

        // Get the node index.
        int nodeIndex = nodes->getNodeIndexByID(shapeId);
        if (nodeIndex >= 0)
        {
            indice.push_back(nodeIndex);
        }

        return indice;
    }

    double FITKAcousticsVTKGraphObjectNodes::getGlyphSize()
    {
        return m_glyphSize;
    }

    bool FITKAcousticsVTKGraphObjectNodes::generateDataSet(Acoustics::FITKAcousticsDiscNodeList* nodes)
    {
        if (!nodes)
        {
            return false;
        }

        // Generate verts.
        int nNodes = nodes->getNodeCount();
        for (int i = 0; i < nNodes; i++)
        {
            Core::FITKNode* sNode = nodes->getNodeAt(i);
            if (!sNode)
            {
                continue;
            }

            // Get the position.
            double pos3[3]{ 0., 0., 0. };
            sNode->getCoor(pos3);

            vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
            cell->InsertNextId(i);
            m_ugrid->InsertNextCell(VTKCellType::VTK_VERTEX, cell);

            m_nodeIdArray->InsertNextValue(sNode->getNodeID());
        }

        // Save the node count.
        m_lastNodeCount = nNodes;

        // Update the size.
        updateGlyphSizeByBounds();

        return true;
    }

    void FITKAcousticsVTKGraphObjectNodes::generateGraph()
    {
        // Create glyph actor.
        m_fActorGlyph = FITKAcousticsVTKGraphActor::New();
        m_fActorGlyph->setGraphObject(this);
        m_fActorGlyph->setActorType(ActorType::OtherActor);
        m_fActorGlyph->SetPickable(false);
        m_fActorGlyph->SetVisibility(true);
        m_fActorGlyph->GetProperty()->SetRepresentation(2);
        m_fActorGlyph->GetProperty()->SetEdgeVisibility(true);
        m_fActorGlyph->GetProperty()->SetLineWidth(1.);
        addActor(m_fActorGlyph);

        // Set the cube glyph color.
        double color3[3]{ 1., 1., 1. };
        FITKAcousticsVTKCommons::QColorToDouble3(FITKAcousticsVTKCommons::s_SEANodesColor, color3);
        m_fActorGlyph->GetProperty()->SetColor(color3);

        // Initialize the input connection.
        m_fActorGlyph->setInputConnection(m_glyph->GetOutputPort());
    }

    vtkDataSet* FITKAcousticsVTKGraphObjectNodes::getMesh(FITKAcousticsVTKCommons::ShapeType type)
    {
        Q_UNUSED(type);
        return m_ugrid;
    }

    bool FITKAcousticsVTKGraphObjectNodes::getFixedBounds(double* bounds)
    {
        if (!m_fActorGlyph)
        {
            return false;
        }

        if (!m_fActorGlyph->GetVisibility())
        {
            return false;
        }

        FITKAcousticsVTKSharedDataManager::GetInstance()->getSEANodesBounds(bounds);
        return true;
    }
}   // namespace Exchange